﻿using dis.sys.hw1.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Dispatcher;
using System.Text;

namespace dis.sys.hw1.Contract
{
    public class QueryCacher : IOperationInvoker
    {

        IOperationInvoker innerOperationInvoker;

        public QueryCacher(IOperationInvoker innerOperationInvoker)
        {
            this.innerOperationInvoker = innerOperationInvoker;
        }
        
        //IList<Hotel> Search(long fromDateTicks, long toDateTicks, double minStars, double minRating);
        public object Invoke(object instance, object[] inputs, 
            out object[] outputs)
        {
            var service = instance as ISearchService;
            var cacheLineKey = GenerateCacheLineKey(inputs);
            Tuple<DateTime, IList<Hotel>> cacheLineValue = null;

            if (service.QueryCache.TryGetValue(cacheLineKey, out cacheLineValue))
            {
                Console.WriteLine("using cache value: " + cacheLineKey);
                outputs = new object[0];
                return cacheLineValue.Item2;
            }
            else
            {
                Console.WriteLine("using real value: " + cacheLineKey);
                var list = (IList<Hotel>)this.innerOperationInvoker.Invoke(
                    instance, inputs, out outputs);
                cacheLineValue = new Tuple<DateTime,IList<Hotel>>(DateTime.UtcNow, list);
                service.QueryCache[cacheLineKey] = cacheLineValue;
                while (service.QueryCache.Count() > 100)
                {
                    var target = service.QueryCache.OrderBy(cl => cl.Value.Item1).FirstOrDefault();
                    service.QueryCache.Remove(target);
                }
                return cacheLineValue.Item2;
            }
        }

        public Tuple<long, long, double, double> GenerateCacheLineKey(object[] inputs)
        {
            return new Tuple<long, long, double, double>(
                (long)inputs[0],
                (long)inputs[1],
                (double)inputs[2],
                (double)inputs[3]);
        }

        public object[] AllocateInputs()
        {
            return innerOperationInvoker.AllocateInputs();
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            return innerOperationInvoker.InvokeEnd(instance, out outputs, result);
        }

        public bool IsSynchronous
        {
            get { return innerOperationInvoker.IsSynchronous; }
        }
    }
}
